/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gov.nist.ssd.swqb.gui.components.graph;

import com.hp.hpl.jena.rdf.model.RDFNode;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxMorphing;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;
import gov.nist.ssd.swqb.commands.CommandManager;
import gov.nist.ssd.swqb.commands.SetFocus;
import gov.nist.ssd.swqb.core.Display;
import gov.nist.ssd.swqb.core.Statement;
import gov.nist.ssd.swqb.core.files.OptionManager;
import gov.nist.ssd.swqb.events.ChangeDisplayCountEvent;
import gov.nist.ssd.swqb.events.ChangeIndexEvent;
import gov.nist.ssd.swqb.events.SetFocusEvent;
import gov.nist.ssd.swqb.gui.MainView;
import gov.nist.ssd.swqb.listeners.ChangeDisplayCountListener;
import gov.nist.ssd.swqb.listeners.ChangeIndexListener;
import gov.nist.ssd.swqb.listeners.SetFocusListener;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;

/**
 *
 * @author benjamin herbomez
 */
public class GraphView extends mxGraphComponent {

    private Display display;
    private CommandManager commandManager;
    private List<Statement> statements = new ArrayList<Statement>();
    private Map<RDFNode, Object> linkGraphJGraph = new HashMap<RDFNode, Object>();
    private Map<Object, RDFNode> rdfNodeMap = new HashMap<Object, RDFNode>();
    private CustomCircleLayout layout;
    private MainView mParent;
    private Dimension size;
    private OptionManager optionManager;

    public GraphView(MainView p, CommandManager cm, OptionManager om) {
        super(new mxGraph());
        this.mParent = p;
        this.commandManager = cm;
        this.optionManager = om;

        this.init();
    }

    public void setDisplay(Display d) {
        this.display = d;
        d.addSetFocusListener(new SetFocusListener() {

            @Override
            public void setFocusStart(SetFocusEvent e) {
                //Nothing to do
            }

            @Override
            public void setFocusEnd(SetFocusEvent e) {
                update();
            }
        });
        d.addChangeIndexListener(new ChangeIndexListener() {

            @Override
            public void indexChanged(ChangeIndexEvent e) {
                update();
            }
        });
        d.addChangeDisplayCountListener(new ChangeDisplayCountListener() {

            @Override
            public void displayCountChanged(ChangeDisplayCountEvent e) {
                update();
            }
        });
        this.update();
    }

    public Display getDisplay() {
        return this.display;
    }

    public void update() {
        if (this.display == null) {
            return;
        }
        Object parent = this.getGraph().getDefaultParent();
        List<Statement> stmts = this.display.getPageContent();

        getGraph().getModel().beginUpdate();

        this.removeOld(stmts);
        this.addNew(stmts,parent);

        this.statements = stmts;

        //Some clean-up
        for (RDFNode k : this.linkGraphJGraph.keySet()) {
            this.getJGraphEdge(k); //will remove the page indication of the other nodes
        }

        //Put cells on the front
        Collection<Object> values = this.linkGraphJGraph.values();
        this.graph.orderCells(false, values.toArray());

        getGraph().getModel().endUpdate();
        
        this.morph(parent);

    }

    private void removeOld(List<Statement> stmts) {
        mxGraph visualGraph = this.getGraph();
        List rm = new ArrayList<Object>();

        //Remove olds nodes but keep the nodes which are in the new
        List<RDFNode> lNew = new ArrayList<RDFNode>();
        for (Statement s : stmts) {
            RDFNode sub = s.getSubject(), obj = s.getObject();
            if (!lNew.contains(sub)) {
                lNew.add(sub);
            }
            if (!lNew.contains(obj)) {
                lNew.add(obj);
            }
        }

        for (Statement s : this.statements) {
            RDFNode sub = s.getSubject(), obj = s.getObject();
            if (!lNew.contains(sub)) {
                rm.add(this.linkGraphJGraph.get(sub));
                this.linkGraphJGraph.remove(sub);
            }
            if (!lNew.contains(obj)) {
                rm.add(this.linkGraphJGraph.get(obj));
                this.linkGraphJGraph.remove(obj);
            }
        }

        visualGraph.removeCells(rm.toArray());
    }

    private void addNew(List<Statement> stmts, Object parent) {
        for (Statement s : stmts) {
            if (this.statements.contains(s)) {
                continue;
            }
            Object v1 = getJGraphEdge(s.getSubject());
            Object v2 = getJGraphEdge(s.getObject());
            getGraph().insertEdge(parent, null, Display.getUri(s.getPredicate()), v1, v2);
        }
    }

    private void morph(Object parent){
        Object main = getJGraphEdge(this.display.getCurrentFocus());
        layout.setMain(main);

        getGraph().getModel().beginUpdate();
        layout.execute(parent);

        int delay = 50;
        Float d = this.optionManager.getOptionAsFloat("animation_Delay");
        if(d != null)
            delay = (int) (100 * d);
        int maxFrame = (int) (d*30);
        mxMorphing morph = new mxMorphing(this, maxFrame, 1.2, delay);
        morph.addListener(mxEvent.DONE, new mxIEventListener() {

            @Override
            public void invoke(Object o, mxEventObject eo) {
                getGraph().getModel().endUpdate();
            }
        });
        morph.startAnimation();
    }
    
    private Object getJGraphEdge(RDFNode e) {
        String txt = Display.getUri(e);
        int h = 30;

        if (e.equals(display.getCurrentFocus())) {
            txt += "\n\tpage: " + (display.getCurrentPageIndex() + 1) + "/" + display.getPageCount();
            h = 45;
        }
        if (this.linkGraphJGraph.containsKey(e)) {
            mxCell cell = (mxCell) this.linkGraphJGraph.get(e);
            cell.setValue(txt);
            mxGeometry geometry = cell.getGeometry();
            geometry.setHeight(h);
            cell.setGeometry(geometry);
            return this.linkGraphJGraph.get(e);
        }
        Object parent = this.getGraph().getDefaultParent();

        Object ne = this.getGraph().insertVertex(parent, null, txt,
                this.size.width / 2 - txt.length() * 3.5, this.size.height / 2 - 15, txt.length() * 6.3, h);


        graph.getModel().isVertex(ne);

        this.linkGraphJGraph.put(e, ne);
        this.rdfNodeMap.put(ne, e);
        return ne;
    }

    public void clear() {
        graph.removeCells(graph.getChildVertices(graph.getDefaultParent()));
        this.linkGraphJGraph.clear();
        this.rdfNodeMap.clear();
        this.statements.clear();
    }

    private void init() {
        size = this.getSize();
        graph.setVertexLabelsMovable(false);
        graph.setEdgeLabelsMovable(false);
        graph.setCellsEditable(false);
        graph.setCellsResizable(false);
        graph.setCellsDisconnectable(false);
        graph.setAutoSizeCells(true);
        graph.setAllowDanglingEdges(false);
        graphHandler.setImagePreview(false);
        graphHandler.setLivePreview(false);
        graphHandler.setPreviewBounds(null);
        graphHandler.setMarkerEnabled(false);
        graphHandler.setCloneEnabled(false);


        layout = new CustomCircleLayout(graph, size);

        ComponentListener cl = new ComponentListener() {

            @Override
            public void componentResized(ComponentEvent ce) {
                size = getSize();
                layout.setSize(size);
                update();
            }

            @Override
            public void componentMoved(ComponentEvent ce) {
            }

            @Override
            public void componentShown(ComponentEvent ce) {
            }

            @Override
            public void componentHidden(ComponentEvent ce) {
            }
        };
        
        this.addComponentListener(cl);

        this.getGraphControl().addMouseListener(new MouseAdapter() {

            @Override
            public void mouseReleased(MouseEvent e) {
                Object cell = getCellAt(e.getX(), e.getY());
                if (cell != null && rdfNodeMap.get(cell) != null && !rdfNodeMap.get(cell).equals(display.getCurrentFocus())) {
                    commandManager.exec(new SetFocus(display, rdfNodeMap.get(cell)));
                }
            }
        });
    }
}
